package hivMultiCompartment;

import java.awt.Point;
import java.util.ArrayList;
import java.util.HashSet;


public class Virus extends MobileObject {	
	
	public ArrayList<Virus> myVirusList;
	//accumulative probability to move to different directions
	//probStay + 4*probSide + 4*probCorner = 1
	static double probStay = GlobalConstants.virusprobMoveStay;
	static double probSide = GlobalConstants.virusprobMoveSide;
	static double probCorner = GlobalConstants.virusprobMoveCorner;
	static double probN = probStay + probSide;
	static double probS = probN + probSide;
	static double probW = probS+ probSide;
	static double probE = probW+ probSide;
	static double probNW = probE + probCorner;
	static double probNE = probNW + probCorner;
	static double probSW = probNE + probCorner;
	
	public int myStrain;
	
	public enum AgentState{
		ACTIVE, DORMANT
	}
	
	public AgentState state;
	
	// the rest will be probSW
	public Virus() {
		super ();
		avgLife = GlobalConstants.avgLifeVirusActive;
		life = 1;
		
		moveInterval = GlobalConstants.moveIntervalVirus;
		
		probEnterLymph = GlobalConstants.probEnterLymphVirusActive;
		probEnterThymus = probEnterLymph + GlobalConstants.probEnterThymusVirusActive;
		probExit = GlobalConstants.probExitVirusActive;
		
		state = AgentState.ACTIVE;
		myStrain = 1;
		//myStrain = Model.getUniformIntFromTo(1, GlobalConstants.maxStrains);
	}
	
	
	
	/**
	 * overloads the MobileObject version: try to move to the eight neighbor cells, or stay.
	 */
	public boolean agentMove(){
		// choose a direction to move
		for(int i=0; i < 5; ++i){
			Point pt = pickDirection();
			Organ2D.virusSpace.moveObjectTo((int)pt.getX(), (int)pt.getY(), this);
		}
		boolean exited = tryToExit();
		return exited;
	}
	
	public Point pickDirection (){
		Point pt;
		double r = Model.getUniformDoubleFromTo(0.0,1.0);
		
		if (r < probStay){
			pt = new Point(x, y);
		}else if (r < probN){
			pt = new Point(x, y-1);
		}else if (r < probS){
			pt = new Point(x, y+1);
		}else if (r < probW){
			pt = new Point(x-1, y);
		}else if (r < probE){
			pt = new Point(x+1, y);
		}else if (r < probNW){
			pt = new Point(x-1, y-1);
		}else if (r < probNE){
			pt = new Point(x+1, y-1);
		}else if (r < probSW){
			pt = new Point(x-1, y+1);
		}else{
			pt = new Point(x+1, y+1);
		}
		return pt;
	}

	/**
	 * overloads the MobileObject version: only look at the current location.
	 */
	public OutputPort getAnExit() {
		OutputPort p = null;
		Object o = Organ2D.cellSpace.getObjectAt(getX(), getY());
		//if this is an output port and the 
		if(o != null){
			if(OutputPort.class.isInstance(o)) {
				//and if virus is allowed output from this port
				if (((OutputPort)o).isExit(this)) {
					p = (OutputPort)o;
				}
			}
			
		}
		return p;
	}
	
	/**
	 * when created or imported or recruited to an Organ.
	 */
	public void updateMyList(ArrayList<Virus> list){
		myVirusList = list;
	}
	/**
	 * when exiting an organ.
	 */
	public void clearMyList(){
		myVirusList = null;
	}
	
	public boolean processLife(){
		double r = Model.getUniformDoubleFromTo(0, 1);
		if (r < 1.0/GlobalConstants.avgLifeVirusActive) {
			agentDie();
			return false;
		}
		return true;
	}
	/**
	 * overloads the MobileObject version.
	 */
	public void agentDie() {
		super.agentDie();
		if (Organ2D!= null) {
			Organ2D.virusSpace.removeObjectFromGrid(this);
		}
		clearMyList();
		alive = false;
	}
	
	/**
	 * 
	 */
	public void infectHostCell(){
		//same as dead: only has DNA in host cell. 
		//removed from virus space and mark as dead so that it will be later removed from virus list.
		mutate();
		agentDie();
		changeState(AgentState.DORMANT);
	}
	
	/**
	 * 
	 */
	public void reactivate(OrganGeneral organ, int newX, int newY){
		//update instance fields
		alive = true;
		changeState(AgentState.ACTIVE);
		resetOrgan(organ);
		updateMyList(organ.virusList);
		x = newX;
		y = newY;
		// the orginal copy does not live
		
		int nrRelease  = GlobalConstants.sampleNormal(GlobalConstants.avgReleaseCount);
		//System.out.println("reactivate: "+ nrRelease);
		for (int i = 0; i < nrRelease; ++i) {
			Virus nv = virusReplication(this);
			nv.resetLifeAndAddToGrid();
		}
//		System.out.println(this.myStrain);
		//put back to list and maybe grid; 
		//bud;
	}
	
	private Virus virusReplication(Virus v){
		try {
			Virus nv = (Virus)v.clone();
			return nv;
		} catch (CloneNotSupportedException e) {
			System.out.println("Virus replication not working!");
			e.printStackTrace();
			return null;
		}
	}
	/**
	 * put new released virus into list and grid
	 *
	 */
	public void resetLifeAndAddToGrid(){
		life = 1;
		myVirusList.add(this);
		if(Organ2D != null) {
			Organ2D.virusSpace.addObjectAt(x, y, this);
		}
	}
	
	/**
	 * 
	 */
	public void mutate(){
		double r = Model.getUniformDoubleFromTo(0, 1);
		if(r < GlobalConstants.virusMutationRate) {
			myStrain = Model.getUniformIntFromTo(1, GlobalConstants.maxStrains);
		}
	}
	
	public void changeState(AgentState s) {
		state = s;
	}
	
	public void statesAndInteractions(){
		//deal with antibodies
		if(Organ2D != null) {
			double prb = probNeutralization();
			double r = Model.getUniformDoubleFromTo(0, 1);
			if(r < prb){
				//neutralized
				agentDie();
				//System.out.println("virus killed in LN!");
				Organ2D.addAntibodyAt(x, y, -GlobalConstants.antibodyEfficiency);
			}
		}
		else{
			if(OrganNS.lymphNodeVirusCount != 0){
				double r = Model.getUniformDoubleFromTo(0, 1);
				if(r < OrganNS.probNeutralizing) {
					//System.out.println("virus killed in blood!");
					agentDie();
				}
			}
		}
	}
	/**
	 * calculate probability of been neutralized
	 * if negative, return 0.
	 */
	public double probNeutralization(){
		double prb;
		HashSet<Virus> vList = (HashSet<Virus>)Organ2D.virusSpace.getObjectAt(x, y);
		int cVirus = vList.size();
		double cAb = Organ2D.antibodySpace.getValueAt(x, y);
		double ratioAbVirus = cAb/cVirus;
		double productAbVirus = cAb*cVirus;
		prb = 2*Math.sqrt(ratioAbVirus)/(ratioAbVirus + GlobalConstants.antibodyEfficiency);
		prb *= productAbVirus * (productAbVirus + GlobalConstants.antibodyVirusHalfMaxProductLocal);
		return prb;
	}
}
